/*

 The circuit:
  * CS - to digital pin 10  (SS pin)
  * SDI - to digital pin 11 (MOSI pin)
  * SDO - to digital pin 12 (MISO pin)
  * CLK - to digital pin 13 (SCK pin)
 
*/

#include "Flash.h"
#include <Serial.h>

// Defines ////////////////////////////////////////////////////////////////

#define BLOCK_ERASE	0xd8
#define GET_FEATURE	0x0f
  #define FEATURE_BLOCK_LOCK  0xA0
  #define FEATURE_OTP         0xB0
  #define FEATURE_STATUS      0xC0
#define PAGE_READ	0x13
#define PROGRAM_EXECUTE	0x10
#define PROGRAM_LOAD	0x02
#define PROGRAM_LOAD_RANDOM_DATA 0x84
#define READ_FROM_CACHE	  0x03
#define READ_FROM_CACHE_2 0x3b
#define READ_FROM_CACHE_4 0x6b
#define READ_ID	0x9f
#define RESET	0xff
#define SET_FEATURE	0x1f
#define WRITE_DISABLE	0x04
#define WRITE_ENABLE	0x06
#define DUMMY_BYTE      42

const int slaveSelectPin = 8;
const int writeProtectPin = 9;
const int holdPin = 10;
const int mosiPin = 11;
const int misoPin = 12;
const int sckPin = 13;

// Constructors ////////////////////////////////////////////////////////////////

Flash::Flash(uint32_t startAddress)
{
  SPI.begin(); 
  
  SPI.setDataMode(SPI_MODE0);
  
  //SPCR |= 0x04;
  Serial.begin(19200);
  pinMode(slaveSelectPin, OUTPUT);
  digitalWrite(slaveSelectPin, HIGH);
  digitalWrite(holdPin, HIGH);
  digitalWrite(writeProtectPin, LOW);
  
  this->state = STATE_NONE;
}

// Public Methods //////////////////////////////////////////////////////////////

Result Flash::writeByte(byte data)
{
  ;
}

// Private Methods ////////////////////////////////////////////////////////////

boolean Flash::changeState(State changeTo)
{
  switch (this->state)
  {
    case STATE_PAGE_READ:
    case STATE_PROGRAM_EXECUTE:
    {
      Serial.println("Checking for OIDP");
      if (this->operationInProgress())
      {
        return false;
        break;
      }
      Serial.println("Operation Finished");
    }
    case STATE_NONE:
    {
      this->state = changeTo;
      return true;
      break;
    }
    default:
    {
      return false;
      break;
    }
  }
}

void Flash::writeEnable()
{
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(WRITE_ENABLE);
  digitalWrite(slaveSelectPin, HIGH);
}

void Flash::programExecute(uint32_t address)
{
  if (this->changeState(STATE_PROGRAM_EXECUTE))
  {
    digitalWrite(slaveSelectPin, LOW);
    SPI.transfer(PROGRAM_EXECUTE);
    SPI.transfer((address >> 16) & 0xFF);
    SPI.transfer((address >> 8) & 0xFF);
    SPI.transfer(address & 0xFF);
    digitalWrite(slaveSelectPin, HIGH);
  }
  else
  {
    Serial.println("State Failure");
  }
}

void Flash::programLoadStart(int plane, uint16_t column)
{
  if (this->changeState(STATE_PROGRAM_LOAD))
  {
    this->writeEnable();
    uint16_t address = ((plane & 0x01) << 12) | (column & 0x0FFF);
    digitalWrite(slaveSelectPin, LOW);
    SPI.transfer(PROGRAM_LOAD);
    SPI.transfer((address >> 8) & 0xFF); // Shift out 8 high bits
    SPI.transfer(address && 0xFF); // Shift out 8 low bits
    this->state = STATE_PROGRAM_LOAD;
  }
  else
  {
    Serial.println("State Failure");
  }
}

void Flash::programLoad(byte data)
{
  if (this->state == STATE_PROGRAM_LOAD)
  {
    SPI.transfer(data);
  }
  else
  {
    Serial.println("State Failure");
  }
}

void Flash::programLoadEnd()
{
  if (this->state == STATE_PROGRAM_LOAD)
  {
    digitalWrite(slaveSelectPin, HIGH);
    state = STATE_NONE;
  }
  else
  {
    Serial.println("State Failure");
  }
}

void Flash::readFromCacheStart(int plane, uint16_t column)
{
  if (this->changeState(STATE_READ_FROM_CACHE))
  {
    uint16_t address = ((plane & 0x01) << 12) | (column & 0x0FFF); 
    digitalWrite(slaveSelectPin, LOW);
    SPI.transfer(READ_FROM_CACHE);
    SPI.transfer((address >> 8) & 0xFF); // Shift out 8 high bits
    SPI.transfer(address && 0xFF); // Shift out 8 low bits
    SPI.transfer(DUMMY_BYTE);   
  }
  else
  {
    Serial.println("State Failure");
  }
}

int Flash::readFromCache()
{
  if (this->state == STATE_READ_FROM_CACHE)
  {
    return SPI.transfer(DUMMY_BYTE);
  }
  else
  {
    Serial.println("State Failure");
  }
  return 0;
}

void Flash::readFromCacheEnd()
{
  if (this->state == STATE_READ_FROM_CACHE)
  {
    digitalWrite(slaveSelectPin, HIGH);
    state = STATE_NONE;
  }
  else
  {
    Serial.println("State Failure");
  }
}

void Flash::pageRead(uint32_t address)
{
  if (this->changeState(STATE_PAGE_READ))
  {
    digitalWrite(slaveSelectPin, LOW);
    SPI.transfer(PAGE_READ);
    SPI.transfer((address >> 16) & (0x03)); // Only the most significant 2 bits are valid.
    SPI.transfer((address >> 8) & (0xFF)); 
    SPI.transfer((address) & (0xFF));
    digitalWrite(slaveSelectPin, HIGH);
  }
  else
  {
    Serial.println("State Failure");
  }
}

void Flash::reset()
{
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(RESET);
  digitalWrite(slaveSelectPin, HIGH);
}

int Flash::operationInProgress()
{
  int f = getFeature(FEATURE_STATUS); //& 0x01;
  Serial.println(f);
  return f & 0x01;
}


int Flash::getFeature(int address)
{
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(GET_FEATURE);
  SPI.transfer(address);
  int feature = SPI.transfer(DUMMY_BYTE);
  digitalWrite(slaveSelectPin, HIGH);
  return feature;
}

uint16_t Flash::readID()
{
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(READ_ID);
  SPI.transfer(DUMMY_BYTE);
  uint16_t id = SPI.transfer(DUMMY_BYTE) << 8;
  id |= SPI.transfer(DUMMY_BYTE);
  digitalWrite(slaveSelectPin, HIGH);
  return id;
}

